﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Windows.Forms;

namespace Launcher
{
    static class Program
    {
        public static List<String> skipFileList = new List<String>();
        public static List<String> skipDirectoryList = new List<String>();
        public static String PackageName = String.Empty;
        public static String estimatedInstallTime = String.Empty;
        public static bool showSplashScreen = false;
        public static bool rebootReminder = false;
        public static String LogDirectory = @"C:\windows\General Mills, Inc\2.0";
        public static String LogPath;
        public static Process parentProcess;
        public static Process splashScreen;
        
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static int Main(string[] args)
        {
            int returnStatus = 0;
            DirectoryInfo cwd = new DirectoryInfo(Directory.GetCurrentDirectory());
            FileInfo manifestXML;
            if (args.Length == 0) { manifestXML = new FileInfo(Path.Combine(cwd.FullName, "Manifest.xml")); }
            else
            {
                if (args.Length == 1) { manifestXML = new FileInfo(Path.Combine(cwd.FullName, args[0].ToString())); }
                else
                {
                    string manifestPath = string.Empty;
                    foreach (string arg in args)
                    { 
                        manifestPath += arg + " "; 
                    }
                    manifestPath = manifestPath.Substring(0, manifestPath.Length - 1);
                    manifestXML = new FileInfo(Path.Combine(cwd.FullName, manifestPath));
                }
            }
            
            List<Command> commandList = new List<Command>();
            parentProcess = ParentProcessUtilities.GetParentProcess();

            if (manifestXML.Exists)
            {
                commandList = ParseManifest(manifestXML);
                if (checkSkipFiles())
                {
                    startLog();
                    if (showSplashScreen)
                    {
                        ProcessStartInfo psi = new ProcessStartInfo();

                        StringBuilder argBuilder = new StringBuilder();

                        argBuilder.Append("\"" + PackageName + "\"" + " ");
                        if (estimatedInstallTime.Equals(String.Empty)) { argBuilder.Append("\"" + " "); }
                        else { argBuilder.Append("\"" + estimatedInstallTime + "\"" + " "); }
                        argBuilder.Append("\"" + rebootReminder + "\"");

                        psi.Arguments = argBuilder.ToString();
                        psi.FileName = "Resources\\SplashScreen.exe";

                        splashScreen = new Process();
                        splashScreen.StartInfo = psi;
                        splashScreen.Start();

                        returnStatus = runCommands(commandList);
                        endLog();
                        try { splashScreen.Kill(); }
                        catch { }
                    }
                    else
                    {
                        returnStatus = runCommands(commandList);
                        endLog();
                    }
                    
                    
                }
                else
                {
                    return 32;
                }
            }
            else
            {
                returnStatus = 31;
            }
            return returnStatus;
        }
        #region Manifest Functions
        public static List<Command> ParseManifest(FileInfo manifestXML)
        {
            List<Command> commandList = new List<Command>();

            XmlDocument doc = new XmlDocument();
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);

            nsmgr.AddNamespace("manifest", "urn:Manifest-Schema");
            doc.Load(manifestXML.FullName);

            XmlNode root = doc.DocumentElement;

            foreach (XmlNode node in root.ChildNodes)
            {
                switch (node.Name)
                {
                    case "Configuration":
                        foreach (XmlNode configNode in node.ChildNodes)
                        {
                            switch (configNode.Name)
                            {
                                case "ShowSplashScreen":
                                    showSplashScreen = Convert.ToBoolean(configNode.InnerText);
                                    break;
                                case "PackageName":
                                    PackageName = configNode.InnerText;
                                    break;
                                case "RebootReminder":
                                    rebootReminder = Convert.ToBoolean(configNode.InnerText);
                                    break;
                                case "EstimatedInstallTime":
                                    estimatedInstallTime = configNode.InnerText;
                                    break;
                                case "SkipFiles":
                                    foreach (XmlNode skipFile in configNode.ChildNodes)
                                    {
                                        switch (skipFile.Name)
                                        {
                                            case "File":
                                                skipFileList.Add(skipFile.InnerText);
                                                break;
                                            case "Directory":
                                                skipDirectoryList.Add(skipFile.InnerText);
                                                break;
                                            default:
                                                skipFileList.Add(skipFile.InnerText);
                                                break;
                                        }
                                    }
                                    break;
                                default:
                                    break;
                            }
                        }
                        break;
                    case "Commands":
                        foreach (XmlNode commandNode in node.ChildNodes)
                        {
                            parseCommandNode(commandList, commandNode);
                        }
                        break;
                }
            }

            return commandList;
        }
        private static void parseCommandNode(List<Command> commandList, XmlNode commandNode)
        {
            Command command = new Command();
            command.Arguments = new List<String>();

            switch (commandNode.Name.ToLower())
            {
                case "logmessage":
                    command.Executable = "LogMessage";
                    foreach (XmlNode commandParamNode in commandNode.ChildNodes)
                    {
                        switch (commandParamNode.Name.ToLower())
                        {
                            case "arguments":
                                foreach (XmlNode arg in commandParamNode.ChildNodes)
                                {
                                    command.Arguments.Add(arg.InnerText);
                                }
                                break;
                            case "osarchitecture":
                                command.osArchitecture = commandParamNode.InnerText;
                                break;
                            case "runonexitcode":
                                foreach (string exitCode in commandParamNode.InnerText.Split(','))
                                {
                                    command.runOnExitCode.Add(exitCode.ToLower());
                                }
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                case "exit":
                    command.Executable = "exitlauncher";
                    command.Arguments.Add("Hard Exit");
                    foreach (XmlNode commandParamNode in commandNode.ChildNodes)
                    {
                        switch (commandParamNode.Name.ToLower())
                        {
                            case "runonexitcode":
                                foreach (string exitCode in commandParamNode.InnerText.Split(','))
                                {
                                    command.runOnExitCode.Add(exitCode.ToLower());
                                }
                                break;
                            case "arguments":
                                command.Arguments.Clear();
                                foreach (XmlNode arg in commandParamNode.ChildNodes)
                                {
                                    command.Arguments.Add(arg.InnerText);
                                }
                                break;
                            default:
                                break;
                        }
                    }
                    break;
                default:
                    foreach (XmlNode commandParamNode in commandNode.ChildNodes)
                    {
                        switch (commandParamNode.Name.ToLower())
                        {
                            case "executable":
                                command.Executable = commandParamNode.InnerText;
                                break;
                            case "waitforexit":
                                command.WaitForExit = Convert.ToBoolean(commandParamNode.InnerText);
                                break;
                            case "returnexitcode":
                                command.returnExitCode = Convert.ToBoolean(commandParamNode.InnerText);
                                break;
                            case "arguments":
                                foreach (XmlNode arg in commandParamNode.ChildNodes)
                                {
                                    command.Arguments.Add(arg.InnerText);
                                }
                                break;
                            case "killonidle":
                                command.killOnIdle = Convert.ToBoolean(commandParamNode.InnerText);
                                break;
                            case "osarchitecture":
                                command.osArchitecture = commandParamNode.InnerText;
                                break;
                            case "runonexitcode":
                                foreach (string exitCode in commandParamNode.InnerText.Split(','))
                                {
                                    command.runOnExitCode.Add(exitCode.ToLower());
                                }
                                break;
                            case "childcommands":
                                foreach (XmlNode childCommandNode in commandParamNode.ChildNodes)
                                {
                                    parseCommandNode(command.childCommands, childCommandNode);
                                }
                                break;
                            default:
                                break;
                        }
                    }
                    break;
            }
            commandList.Add(command);
        }
        #endregion
        #region Skip File Functions
        public static bool checkSkipFiles()
        {
            bool configStatus = true;

            foreach (String skipFileLocation in skipFileList)
            {
                configStatus = !(File.Exists(skipFileLocation));
                if (configStatus == false) { break; }
            }
            if (configStatus)
            {
                foreach (String skipDirectoryLocation in skipDirectoryList)
                {
                    configStatus = !(Directory.Exists(skipDirectoryLocation));
                    if (configStatus == false) { break; }
                }
            }

            return configStatus;
        }
        #endregion
        #region loggingMethods
        public static void startLog()
        {
            try
            {
                LogPath = Path.Combine(LogDirectory, PackageName + ".log");

                addHeaderInfo();
                addProcessInfo();
            }
            catch { }
        }
        public static void addHeaderInfo()
        {
            try
            {
                if (!Directory.Exists(LogDirectory)) { Directory.CreateDirectory(LogDirectory); }
                if (File.Exists(LogPath))
                {
                    using (StreamWriter sw = File.AppendText(LogPath))
                    {
                        try { sw.WriteLine("################################################################################"); }
                        catch { }
                        try { sw.WriteLine("--------------------------------------------------------------------------------"); }
                        catch { }
                        try { sw.WriteLine("Package Name:           " + PackageName); }
                        catch { }
                        try { sw.WriteLine("Machine Name:           " + System.Environment.MachineName.ToString()); }
                        catch { }
                        try { sw.WriteLine("Date:                   " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString()); }
                        catch { }
                        try { sw.WriteLine("Launching Application:  " + parentProcess.ProcessName); }
                        catch { }
                        try { sw.WriteLine("Launching User:         " + WindowsIdentity.GetCurrent().Name); }
                        catch { }
                        try
                        {
                            PerformanceCounter upTime = new PerformanceCounter("System", "System Up Time");
                            upTime.NextValue();
                            TimeSpan ts = TimeSpan.FromSeconds(upTime.NextValue());
                            sw.WriteLine("System Uptime:          {0}d {1}h {2}m {3}s", ts.Days, ts.Hours, ts.Minutes, ts.Seconds);
                        }
                        catch { }
                        try { sw.WriteLine("--------------------------------------------------------------------------------"); }
                        catch { }
                    }
                }
                else
                {
                    using (StreamWriter sw = File.AppendText(LogPath))
                    {
                        try { sw.WriteLine("################################################################################"); }
                        catch { }
                        try { sw.WriteLine("--------------------------------------------------------------------------------"); }
                        catch { }
                        try { sw.WriteLine("Package Name:           " + PackageName); }
                        catch { }
                        try { sw.WriteLine("Machine Name:           " + System.Environment.MachineName.ToString()); }
                        catch { }
                        try { sw.WriteLine("Date:                   " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString()); }
                        catch { }
                        try { sw.WriteLine("Launching Application:  " + parentProcess.ProcessName); }
                        catch { }
                        try { sw.WriteLine("Launching User:         " + WindowsIdentity.GetCurrent().Name); }
                        catch { }
                        try
                        {
                            PerformanceCounter upTime = new PerformanceCounter("System", "System Up Time");
                            upTime.NextValue();
                            TimeSpan ts = TimeSpan.FromSeconds(upTime.NextValue());
                            sw.WriteLine("System Uptime:          {0}d {1}h {2}m {3}s", ts.Days, ts.Hours, ts.Minutes, ts.Seconds);
                        }
                        catch { }
                        try { sw.WriteLine("--------------------------------------------------------------------------------"); }
                        catch { }
                    }
                }
            }
            catch (Exception ex) { }
        }
        public static void addProcessInfo()
        {
            try
            {

            Process[] allProcesses = Process.GetProcesses();

                using (StreamWriter sw = File.AppendText(LogPath))
                {
                    try { sw.WriteLine("--------------------------------------------------------------------------------"); }
                    catch { }
                    foreach (Process proc in allProcesses)
                    {
                        try { sw.WriteLine("Process Image Name:   " + proc.ProcessName.ToString()); }
                        catch { }
                        try { sw.WriteLine("Process Description:  " + proc.MainModule.FileVersionInfo.FileDescription); }
                        catch { }
                        try { sw.WriteLine(""); }
                        catch { }
                    }
                    try { sw.WriteLine("--------------------------------------------------------------------------------"); }
                    catch { }
                }
            }
            catch { }
        }
        private static void LogProcessStart(Process p, bool runAsSystem)
        {
            try
            {
                using (StreamWriter sw = File.AppendText(LogPath))
                {
                    sw.WriteLine("--------------------------------------------------------------------------------");
                    sw.WriteLine("Launcher is Starting: " + p.ProcessName);
                    sw.WriteLine("Current Time:         " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString());
                    sw.WriteLine("Aruments:             " + p.StartInfo.Arguments);
                    sw.WriteLine("--------------------------------------------------------------------------------");
                }
            }
            catch { }
        }
        private static void LogProcessEnd(Process p)
        {
            try
            {
                using (StreamWriter sw = File.AppendText(LogPath))
                {
                    sw.WriteLine("--------------------------------------------------------------------------------");
                    sw.WriteLine("Process Has Finished: " + p.ProcessName);
                    sw.WriteLine("Current Time:         " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString());
                    sw.WriteLine("Process Exit Code:    " + p.ExitCode);
                    sw.WriteLine("--------------------------------------------------------------------------------");
                }
            }
            catch { }
        }
        private static void LogInvalidEnvironment()
        {
            try
            {
                using (StreamWriter sw = File.AppendText(LogPath))
                {
                    sw.WriteLine("--------------------------------------------------------------------------------");
                    sw.WriteLine("Exiting Due to Invalid Parent Process (Not SCCM):  " + parentProcess.ProcessName);
                    sw.WriteLine("--------------------------------------------------------------------------------");
                }
            }
            catch { }
        }
        private static void endLog()
        {
            try
            {
                using (StreamWriter sw = File.AppendText(LogPath))
                {
                    sw.WriteLine("################################################################################");
                }
            }
            catch { }
        }
        private static void logError(Exception ex, Command c)
        {
            try
            {
                using (StreamWriter sw = File.AppendText(LogPath))
                {
                    sw.WriteLine("--------------------------------------------------------------------------------");
                    sw.WriteLine("Launcher encountered an Error with Command " + c.Executable + " Arguments " + c.Arguments + ":  " + ex.Message.ToString());
                    sw.WriteLine("--------------------------------------------------------------------------------");
                }
            }
            catch { }
        }
        private static void logMessage(List<string> messageList)
        {
            try
            {
                using (StreamWriter sw = File.AppendText(LogPath))
                {
                    sw.WriteLine("--------------------------------------------------------------------------------");
                    foreach (string m in messageList)
                    {
                        sw.WriteLine(m);
                    }
                    sw.WriteLine("--------------------------------------------------------------------------------");
                }
            }
            catch { }
        }
        private static void logGenericMessage(string message)
        {
            using (StreamWriter sw = File.AppendText(LogPath))
            {
                sw.WriteLine("--------------------------------------------------------------------------------");
                sw.WriteLine(message);
                sw.WriteLine("--------------------------------------------------------------------------------");
            }
        }
        #endregion
       
        #region command Functions
        public static int runCommands(List<Command> commandList)
        {
            int returnCode = 0;

            foreach (Command command in commandList)
            {
                returnCode = runCommandsCommon(command);
            }
            return returnCode;
        }
        public static int runCommands(List<Command> commandList, string parentProcessExitCode)
        {
            int returnCode = 0;

            foreach (Command command in commandList)
            {
                if (command.runOnExitCode.Contains(parentProcessExitCode.ToLower()) || command.runOnExitCode.Contains("all"))
                {
                    returnCode = runCommandsCommon(command);
                }
            }
            return returnCode;
        }

        private static int runCommandsCommon(Command command)
        {
            int internalReturnCode = 0;
            switch (command.Executable.ToLower())
            {
                case "logmessage":
                    logMessage(command.Arguments);
                    break; 
                case "exitlauncher":
                    logMessage(command.Arguments);
                    endLog();
                    splashScreen.Kill();
                    Environment.Exit(35);
                    break;
                default:
                    internalReturnCode = runCommand(command);
                    break;
            }
            return internalReturnCode;
        }

        private static int runCommand(Command command)
        {
            int internalReturnCode = 0;
            ProcessStartInfo psi = new ProcessStartInfo();

            String argString = String.Empty;

            foreach (string arg in command.Arguments)
            {
                if (argString == String.Empty) { argString = arg; }
                else { argString += " " + arg; }
            }

            psi.Arguments = argString;
            psi.FileName = command.Executable;

            Process p = new Process();

            p.StartInfo = psi;

            switch (command.osArchitecture.ToUpper())
            {
                case "32 BIT":
                    if (IntPtr.Size == 4)
                    {
                        internalReturnCode = runProcess(p, command);
                    }
                    else
                    {
                        internalReturnCode = -999;
                    }
                    break;
                case "64 BIT":
                    if (IntPtr.Size == 8)
                    {
                        internalReturnCode = runProcess(p, command);
                    }
                    else
                    {
                        internalReturnCode = -999;
                    }
                    break;
                case "ANY":
                    internalReturnCode = runProcess(p, command);
                    break;
                case "":
                    internalReturnCode = runProcess(p, command);
                    break;
                default:
                    internalReturnCode = runProcess(p, command);
                    break;
            }
            if (command.returnExitCode && command.WaitForExit && command.childCommands.Count > 0)
            {
                runCommands(command.childCommands, internalReturnCode.ToString());
            }
            return internalReturnCode;
        }
        private static int runProcess(Process p, Command command)
        {
            int returnCode = 0;

            try
            {
                p.Start();
                LogProcessStart(p, command.runAsAdmin);
                try
                {
                    //p.WaitForInputIdle();
                    if (command.killOnIdle)
                    {
                        p.Kill();
                    }
                }
                catch { }

                if (command.WaitForExit) { p.WaitForExit(); LogProcessEnd(p); }
                if (command.returnExitCode) { returnCode = p.ExitCode; }

            }
            catch (Exception ex)
            {
                logError(ex, command);
            }
            return returnCode;
        }
        #endregion
    }
}
